using System;
using System.Text;
using System.Net;
using System.Collections.Generic;
using System.Globalization;

namespace ProtoScratch.Common
{
	public class ScratchMessage
	{
		public readonly byte Channel;
		public readonly short PrimaryCommand, SecondaryCommand;
		private ushort _Flags;
		public ushort Flags
		{
			get { return _Flags; }
			private set { throwWrite(); _Flags = value; }
		}
		
		private readonly bool RX;
		private int Offset;
		private readonly List<byte> Data = new List<byte>();
		public readonly int SessionID;
		
		public bool Compressed
		{
			set { setFlag(ScratchFlags.Compressed, value); }
			get { return getFlag(ScratchFlags.Compressed); }
		}
		
		public bool Encrypted
		{
			set { setFlag(ScratchFlags.Encrypted, value); }
			get { return getFlag(ScratchFlags.Encrypted); }
		}
		
		public ScratchMessage (byte Channel, short PrimaryCommand, short SecondaryCommand)
		{
			this.Channel = Channel;
			this.PrimaryCommand = PrimaryCommand;
			this.SecondaryCommand = SecondaryCommand;
		}
		
		public ScratchMessage (byte Channel, short PrimaryCommand, short SecondaryCommand, byte[] Data, ushort Flags, int SessionID)
			: this (Channel, PrimaryCommand, SecondaryCommand)
		{
			pack(Data);
			_Flags = Flags;
			RX = true;
			this.SessionID = SessionID;
		}
		
		public bool getFlag(ushort flag)
		{
			return (Flags & flag) == flag;
		}
		
		public void setFlag(ushort flag, bool value)
		{
			if (value)
				Flags |= flag;
			else
				Flags &= (ushort)~flag;
		}
		
		private void throwWrite()
		{
			if (RX) throw new ScratchException(ScratchException.eType.ReadOnly, "Write operation on Read Only data.");
		}

		public void resetReader()
		{
			Offset = 0;
		}
		
		public ScratchMessage[] split()
        {
            var messages = new ScratchMessage[(Data.Count / (ushort.MaxValue)) + 1];
            if (messages.Length <= 1)
                messages[0] = this;
            else
            {
                for (int i = 0; i < messages.Length; i++)
                {
					int index = i * (ushort.MaxValue);
					int size = index + ushort.MaxValue <= Data.Count ? ushort.MaxValue : Data.Count - index;
                    messages[i] = new ScratchMessage(Channel, PrimaryCommand, SecondaryCommand);
                    messages[i].setFlag((ushort)(Flags | ScratchFlags.Partial), true);
                    messages[i].pack(Data.GetRange(index, size).ToArray());
                }
                messages[messages.Length - 1].setFlag(ScratchFlags.Partial, false);
            }
            return messages;
        }

		public int dataSize()
		{
			return Data.Count;
		}
		
		public byte[] getData()
		{
			return Data.ToArray();
		}
		
		public bool get(out byte[] rData)
		{
			return get(out rData, Data.Count - Offset);
		}
		
		public bool get(out byte[] rData, int Size)
		{
			if (Offset + Size > Data.Count)
			{
				rData = new byte[0];
				return false;
			}
			rData = Data.GetRange(Offset, Size).ToArray();
			Offset += Size;
			return true;
		}
		
		public bool get(ref byte rByte)
		{
			if (Offset + 1 > Data.Count)
				return false;
			rByte = Data[Offset++];
			return true;
		}

		public bool get(ref Int16 rInt)
		{
			if (Offset + 2 > Data.Count)
				return false;
			rInt = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(Data.ToArray(), Offset));
			Offset += 2;
			return true;
		}
		
		public bool get(ref Int32 rInt)
		{
			if (Offset + 4 > Data.Count)
				return false;
			rInt = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(Data.ToArray(), Offset));
			Offset += 4;
			return true;
		}
		
		public bool get(ref string rString)
		{
			Int16 Length = 0;
			if (get(ref Length) && Offset + Length > Data.Count)
			{
				Offset-=2;
				return false;
			}
			byte[] rawData;
			get (out rawData, Length);
			if (rawData.Length >= 2)
			{
				if (rawData[0] == 0xFE && rawData[1] == 0xFF)
					rString = Encoding.BigEndianUnicode.GetString(rawData,2,rawData.Length - 2);
				else
					rString = Encoding.Unicode.GetString(rawData,2,rawData.Length - 2); //little endian unicode
				return true;
			}
			Offset-=2;
			return false;
		}
		
		public void pack(byte newData)
		{
			throwWrite();
			Data.Add(newData);
		}
		
		public void pack(byte[] NewData)
		{
			throwWrite();
			Data.AddRange(NewData);
		}
		
		public void pack (Int16 newData)
		{
			throwWrite();
			Data.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(newData)));
		}
		
		public void pack(Int16[] NewData)
		{
			throwWrite();
			foreach (Int16 thisData in NewData)
				pack(thisData);
		}
		
		public void pack(Int32 newData)
		{
			throwWrite();
			Data.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(newData)));
		}
		
		public void pack(Int32[] NewData)
		{
			throwWrite();
			foreach (Int32 thisData in NewData)
				pack(thisData);
		}
		
		public void pack(string NewData)
		{
			throwWrite();
			byte[] result = Encoding.Unicode.GetBytes(NewData);
			pack((Int16)(result.Length + 2));
			pack(Encoding.Unicode.GetPreamble());
			pack(result);
		}
	}
}

